package rest.jbpm.services;

import java.io.*;
import java.util.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.dom4j.DocumentException;
import org.jbpm.api.*;
import org.jbpm.api.history.*;
import org.slf4j.*;
import rest.jbpm.JbpmService;
import rest.jbpm.ProcessEngineException;
import rest.jbpm.execution.FindProcessByVariable;
import rest.jbpm.filters.ClearNamespaceFilter;
import rest.jbpm.filters.UnwrappingFilter;
import rest.jbpm.svg.SVGBuilder;
import rest.jbpm.wrappers.Duration;
import rest.jbpm.wrappers.Execution;
import rest.jbpm.wrappers.lists.*;

@Path("exec")
public class ExecutionService  extends JbpmService  {

	private static Logger log = LoggerFactory.getLogger(ExecutionService.class);
	
	@Context UriInfo uriInfo;
	
	@GET
	@Produces("text/xml")
	@Path("execution/{id}")
	public Execution getExecution(@PathParam("id") String id) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		return new Execution(processEngine.getExecutionService().findExecutionById(id),
				uriInfo.getBaseUri().toString());
	}	
	
	@GET
	@Produces("text/xml")
	@Path("execution/{executionId}/variableNames")
	public StringList getVariableNames(@PathParam("executionId") String executionId) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
			
		return new StringList(processEngine.getExecutionService().getVariableNames(executionId));
	}
	 
	@GET
	@Produces("text/xml")
	@Path("execution/{executionId}/variable/{variableName}")
	public String getVariable(@PathParam("executionId") String executionId,
            @PathParam("variableName") String variableName) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		return processEngine.getExecutionService().getVariable(executionId, variableName).toString();
	}
	
	@POST
	@Consumes("application/x-www-form-urlencoded")
	@Path("execution/{executionId}/variables")
	public void setVariables(MultivaluedMap<String, String> formParams,
            @PathParam("executionId") String executionId) {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
		
		log.info("updating process variables for " + executionId + " with vars: ");
		Map<String,Object> vars = new HashMap<String, Object>();
		
		for(String key : formParams.keySet()) {
			
			Object value = formParams.get(key);
			log.info("name: " + key + " value: " + value);
			vars.put(key, value);
			
		}
		processEngine.getExecutionService().setVariables(executionId, vars);
	}

	@GET
	@Produces("text/xml")
	@Path("process/{id}")
	public Execution getProcessInstance(@PathParam("id") String id) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
		
		return new Execution(processEngine.getExecutionService().findProcessInstanceById(id),
				uriInfo.getBaseUri().toString());
	}

    @GET
	@Produces("text/xml")
	@Path("process/{processId}/tasks")
	public TaskList getProcessTasks(@PathParam("processId") String processId) {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
        
        /**
         * performance trick to force hand-made unwrapping over unwrapping filter
         */
        /*if(uriInfo.getQueryParameters().containsKey(UnwrappingFilter.UNWRAP))
            uriInfo.getQueryParameters().remove(UnwrappingFilter.UNWRAP);
        
        uriInfo.getQueryParameters().add(ClearNamespaceFilter.CLEANUP, "true");
        
        return new TaskList(processEngine.execute(new FindTasksInProcessUnwrapped(processId,null)),
            uriInfo.getBaseUri().toString(),true);*/

        
		return new TaskList(
                processEngine.getTaskService().createTaskQuery().executionId(processId).list(),
				uriInfo.getBaseUri().toString());
        
	}

	@GET
	@Produces("image/svg+xml")
	@Path("process/{id}/image")
	public String getProcessImage(@PathParam("id") String id) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
		
		ProcessInstance pi = processEngine.getExecutionService().findProcessInstanceById(id);
        if(pi == null)
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        
		ProcessDefinition pd = processEngine
                    .getRepositoryService()
					.createProcessDefinitionQuery()
                    .processDefinitionId(pi.getProcessDefinitionId()).uniqueResult();

		Set<String> activities = pi.findActiveActivityNames();

        TaskList tasks = getProcessTasks(id);

        DurationList stats = new DurationList(processEngine.getHistoryService()
                .avgDurationPerActivity(pi.getProcessDefinitionId()));

        // Хранилище средних длительностей
        Map<String,Number> durationStats = new HashMap<String, Number>();

        // Хранилище распеделений переходов
        Map<String,Map<String,Number>> choiseStats = new HashMap<String,Map<String,Number>>();

        // Парсим средние длительности и заодно собираем распределения переходов
        for (Duration d:stats.getDurations()){
            durationStats.put(d.getActivity(),d.getDuration());
            choiseStats.put(d.getActivity(),processEngine.getHistoryService().choiceDistribution(pi.getProcessDefinitionId(),d.getActivity()));
        }

		try {
            /* from here */
            File f = new File(RepositoryService.getDeploymentDir(), pd.getId() + ".jpdl.xml");

            SVGBuilder svg = new SVGBuilder();
            svg.addJPDL(f);
            svg.setActiveElements(activities);
            svg.setElementDurations(durationStats);
            svg.setChoiseDistributions(choiseStats);
            svg.build();

            return svg.getSVGAsString();
			
		} catch (FileNotFoundException e1) {
			throw new WebApplicationException(e1,404);
		} catch (IOException e) {
			// lol payment required :)
			throw new WebApplicationException(e,402);
		} catch (DocumentException e){
            throw new WebApplicationException(e,500);
        }
	}
	
	
	@GET
	@Path("process/{Id}/comments")
	@Produces("text/xml")
	public CommentList getComments(@PathParam("Id") String processId) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		ArrayList<HistoryComment> comments = new ArrayList<HistoryComment>();
		for(HistoryDetail hd : processEngine.getHistoryService()
				.createHistoryDetailQuery().processInstanceId(processId).comments().list()) {
			if(hd instanceof HistoryComment)
				comments.add((HistoryComment)hd);
		}
		return new CommentList(comments);
	}
	
	@GET
	@Produces("text/xml")
	@Path("process/{processId}/assignee/{assignee}")
	public TaskList getProcessTasksForAssignee(
            @PathParam("processId") String processId,
			@PathParam("assignee") String assignee) {

        assignee = DNC14N.c14n(assignee);
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");
        
        /**
         * performance trick to force hand-made unwrapping over unwrapping filter
         */
        /*if(uriInfo.getQueryParameters().containsKey(UnwrappingFilter.UNWRAP))
            uriInfo.getQueryParameters().remove(UnwrappingFilter.UNWRAP);
        
        uriInfo.getQueryParameters().add(ClearNamespaceFilter.CLEANUP, "true");
        
        return new TaskList(processEngine.execute(
                new FindTasksInProcessUnwrapped(processId,assignee)),
                uriInfo.getBaseUri().toString(),true);*/

        
		return new TaskList(processEngine.getTaskService().createTaskQuery()
												.processInstanceId(processId)
												.assignee(assignee).list(),
							uriInfo.getBaseUri().toString());
        				
	}
		
	@POST
	@Produces("text/xml")
	@Path("process/{processDefinitionId}/start")
	@Consumes("application/x-www-form-urlencoded")
	public Execution startProcess(MultivaluedMap<String, String> formParams,
            @PathParam("processDefinitionId") String processDefinitionId) {
	
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		log.info("starting process " + processDefinitionId + " with vars: ");
		Map<String,Object> initVars = new HashMap<String, Object>();
		
		for(String key : formParams.keySet()) {
			
			Object value = formParams.get(key);
			log.info("name:{} value:{}",key,value);
            // TODO : remove hardcode
            if(key.equalsIgnoreCase("initiator"))
                initVars.put(key, DNC14N.c14n(value instanceof LinkedList ?
                            ((LinkedList)value).peekFirst().toString() : (String)value));
            else
                initVars.put(key, value instanceof LinkedList ? ((LinkedList)value).peekFirst() : (String)value);
		}
		
		Execution exec = new Execution(processEngine
                    .getExecutionService()
                    .startProcessInstanceById(processDefinitionId,initVars),
                    uriInfo.getBaseUri().toString());
       
        if(!exec.isEnded())
            processEngine.getExecutionService().createVariables(exec.getId(), initVars, true);
        
        return exec;
		
	}

	@POST
	@Produces("text/xml")
	@Path("process/key/{processDefinitionKey}/start")
	@Consumes("application/x-www-form-urlencoded")
	public Execution startProcessByKey(MultivaluedMap<String, String> formParams, 
			@PathParam("processDefinitionKey") String processDefinitionKey) {
	
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		log.info("starting process " + processDefinitionKey + " with vars: ");
		Map<String,Object> initVars = new HashMap<String, Object>();
		
		for(String key : formParams.keySet()) {
			
			Object value = formParams.get(key);
			log.info("name:{} value:{}",key,value);
            // TODO : remove hardcode
            if(key.equalsIgnoreCase("initiator"))
                initVars.put(key, DNC14N.c14n(value instanceof LinkedList ?
                            ((LinkedList)value).peekFirst().toString() : (String)value));
            else
                initVars.put(key, value instanceof LinkedList ? ((LinkedList)value).peekFirst() : (String)value);

		}
        
		Execution exec = new Execution(processEngine.getExecutionService()
                .startProcessInstanceByKey(processDefinitionKey,initVars),
				uriInfo.getBaseUri().toString());

        if(!exec.isEnded())
            processEngine.getExecutionService().createVariables(exec.getId(), initVars, true);

        return exec;
	}
	
	@GET
	@Produces("text/xml")
	@Path("process/{processDefinitionId}/start")
	public Execution startProcess(@PathParam("processDefinitionId") String processDefinitionId) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		return new Execution(processEngine.getExecutionService().startProcessInstanceById(processDefinitionId),
				uriInfo.getBaseUri().toString());
	}
	
	@GET
	@Produces("text/xml")
	@Path("process/key/{processDefinitionKey}/start")
	public Execution startProcessByKey(@PathParam("processDefinitionKey") String processDefinitionKey) {
		
		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		return new Execution(processEngine.getExecutionService().startProcessInstanceByKey(processDefinitionKey),
				uriInfo.getBaseUri().toString());
	}
	
	@POST
	@Path("process/{processInstanceId}/end")
	public void endProcess(@PathParam("processInstanceId") String processInstanceId, @FormParam("state") String state) {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		processEngine.getExecutionService().endProcessInstance(processInstanceId, state);
		
	}
	
	@DELETE
	@Path("process/{processInstanceId}")
	public void deleteProcess(@PathParam("processInstanceId") String processInstanceId) {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		processEngine.getExecutionService().deleteProcessInstance(processInstanceId);
		
	}
	
	@GET
	@Path("processes")
	@Produces("text/xml")
	public ExecutionList getProcesses() {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

		return new ExecutionList(processEngine.getExecutionService().createProcessInstanceQuery().list(),
				uriInfo.getBaseUri().toString());
		
	}
    
    @GET
	@Path("processes/variable/{name}/{value}")
	@Produces("text/xml")
    public ExecutionList getProcessesByVariable(@PathParam("name") String name, 
                @PathParam("value") String value) {

		if(processEngine == null)
			throw new ProcessEngineException("Fire! fire! mayday! mayday! we're going down!.. JBPM process engine is down!");

        return new ExecutionList(processEngine.execute(new FindProcessByVariable(name, value)),
                uriInfo.getBaseUri().toString());
    }

}
